#include "processor.h"
#include <glib.h>

#define MAX_TIER_NUM 1000

typedef struct ProvNodeCtx {
    guint tier;
    double inf_score;
} ProvNodeCtx;

typedef struct TypeInfo {
    #define TYPE_INFO_NAME_BUFSZ 512
    gchar name[TYPE_INFO_NAME_BUFSZ];
    guint num;
} TypeInfo;

typedef struct DetailedGraph {
    ProvGraph *g;
    GList *tier[MAX_TIER_NUM]; // nodes in every tier (sorted)'
    GHashTable *critical_edges;
    GHashTable *aligned_nodes;
    GList *tier_type_info[MAX_TIER_NUM]; // typeinfo in every tier
    GHashTable *tier_type_nodes[MAX_TIER_NUM]; // point to type node array, classfied by type(GPtrArray)
    guint max_tier; // start from 0, the last tier shoud be tier[max_tier]
    enum {
        DG_UNINITIALIZED = 0,
        DG_GRAPH_LOADED = 1,
        DG_TIER_LOADED = 2,
        DG_TIER_TYPE_NODES_LOADED = 4,
        DG_TIER_TYPE_INFO_LOADED = 8,
    } status;
} DetailedGraph;

DetailedGraph *
detailed_graph_init (ProvGraph *g) {
    DetailedGraph *dg;
    dg = g_new0 (DetailedGraph, 1);
    if (dg == NULL) return NULL;
    dg->critical_edges = g_hash_table_new (g_direct_hash, g_direct_equal);
    dg->aligned_nodes = g_hash_table_new (g_direct_hash, g_direct_equal);
    dg->g = g;
    dg->status |= DG_GRAPH_LOADED;
    return dg;
}

/* for ProvNode ctx */
static inline int
clear_prov_node_ctx (ProvNode *p) {
    g_free(p->opaque);
    p->has_ctx = FALSE;
    return 0;
}

static inline int
init_prov_node_ctx (ProvNode *p) {
    p->opaque = g_new0 (ProvNode, 1);
    p->has_ctx = TRUE;
    return 0;
}

static inline int
set_prov_node_ctx_tier (ProvNode *p, guint tier) {
    if (!p->has_ctx) {
        init_prov_node_ctx (p);
    }
    ProvNodeCtx *ctx = p->opaque;
    ctx->tier = tier;
    return 0;
}

int
detailed_graph_load_tier (DetailedGraph *dg, GPtrArray *seeds) {
    // BFS then mark critical edge
    GHashTable *visited_node;
    GQueue *queue;
    visited_node = g_hash_table_new (g_direct_hash, g_direct_hash);
    queue = g_queue_new ();
    guint i;
    guint this_tier = 0;
    ProvNode *tmp_pn,*this_pn, *next_pn;
    ProvEdge *tmp_pe;
    GSList *next_pn_slist;
    ProvNodeCtx *this_ctx;
    // first, we add seed nodes into the BFS queue
    for (i = 0; i < seeds->len; i++) {
        tmp_pn = seeds->pdata[i];
        set_prov_node_ctx_tier (tmp_pn, 0); // set seed nodes as 0 tier
        g_queue_push_tail (queue, tmp_pn);
        g_hash_table_insert (visited_node, tmp_pn, tmp_pn);
    }
    while (!g_queue_is_empty (queue)) {
        this_pn = g_queue_pop_head (queue);
        this_ctx = this_pn->opaque;
        this_tier = this_ctx->tier;
        if (this_tier >= MAX_TIER_NUM) {
            fprintf (stderr, "MAX tier reached\n");
            g_abort ();
        }
        dg->tier[this_tier] = g_list_prepend (dg->tier[this_tier], this_pn);
        if (this_tier > dg->max_tier)
            dg->max_tier = this_tier;
        for (next_pn_slist = this_pn->out; next_pn_slist != NULL; 
                                        next_pn_slist = next_pn_slist->next) {
            tmp_pe = next_pn_slist->data;
            // mark edge as critical
            next_pn = tmp_pe->to;
            if (g_hash_table_contains (visited_node, next_pn))
                continue;
            g_hash_table_add (dg->critical_edges, tmp_pe);
            set_prov_node_ctx_tier (next_pn, this_tier+1);
            g_hash_table_add (visited_node, next_pn);
            g_queue_push_tail (queue, tmp_pe);
        }
    }
    dg->status |= DG_TIER_LOADED;
    return 0;
}

// int
// detailed_graph_align_nodes (DetailedGraph *dg, ) {

// }

gint 
type_info_cmp_func (gconstpointer a, gconstpointer b) {
    const TypeInfo *ta = a, *tb = b;
    return g_str_hash (ta->name) - g_str_hash (tb->name);
}


int
detailed_graph_load_tier_type_detail (DetailedGraph *dg) {
    guint tier;
    GList *pn_iter, *type_info_found;
    TypeInfo tmp_typeinfo, *typeinfo_hint = &tmp_typeinfo, *this_type_info;
    ProvNode *this_pn;
    GPtrArray *node_arr;
    for (tier = 0; tier <= dg->max_tier; tier++) {
        pn_iter = dg->tier[tier];
        while (pn_iter != NULL) {
            this_pn = pn_iter->data;
            /* for type info */
            g_strlcpy (typeinfo_hint->name, this_pn->type, TYPE_INFO_NAME_BUFSZ);
            type_info_found = g_list_find_custom (dg->tier_type_info[tier], typeinfo_hint,
                                                  type_info_cmp_func);
            if (type_info_found != NULL) {
                this_type_info = type_info_found->data;
                this_type_info->num++;
            } else {
                this_type_info = g_new0 (TypeInfo, 1);
                g_strlcpy (this_type_info->name, this_pn->type, TYPE_INFO_NAME_BUFSZ);
                this_type_info->num = 1;
                dg->tier_type_info[tier] = g_list_prepend (dg->tier_type_info[tier], this_type_info);
            }

            /* for type node list */
            if (dg->tier_type_nodes[tier] == NULL) {
                dg->tier_type_nodes[tier] = g_hash_table_new (g_str_hash,g_str_equal);
            }
            node_arr = g_hash_table_lookup (dg->tier_type_nodes[tier], this_pn->type);
            if (node_arr == NULL) {
                node_arr = g_ptr_array_new ();
                g_hash_table_insert (dg->tier_type_nodes[tier], this_pn->type, node_arr);
            }
            g_ptr_array_add (node_arr, this_pn);

            pn_iter = pn_iter->next;
        }
    }
    dg->status |= (DG_TIER_TYPE_INFO_LOADED | DG_TIER_TYPE_NODES_LOADED);
    return 0;
}